﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IMIForUnity;
using System.Runtime.InteropServices;
using UnityEngine;
namespace IMIForUnity
{
    public class ImiGestureManager
    {
        private Dictionary<System.Type,ImiGesture> playerGestures;
        private static Dictionary<ImiGesturePro.GestureType, ImiGesturePro> playerGesturesPro
            = new Dictionary<ImiGesturePro.GestureType, ImiGesturePro>();

        private List<System.Type> playerGesturesCopy;
        private static List<ImiGesturePro.GestureType> playerGesturesProCopy
            = new List<ImiGesturePro.GestureType>();

        private DateTime BaseTime;
        private static ImiGestureManager instance = null;
        private static object obj = new object();

        private static System.IntPtr recognizedPtr;
        private static System.IntPtr recognizingPtr;
        private bool proInited = false;
        private static MainThreadDispatcher threadDispatcher = MainThreadDispatcher.GetInstance();
        public bool ProInited { get { return proInited; } }
        private object locker = new object();


        public static ImiGestureManager GetInstance()
        {
            if (instance == null)
            {
                lock (obj)
                {
                    if (instance == null)
                    {
                        instance = new ImiGestureManager();
                    }
                }
            }
            return instance;
        }


        public ImiGestureManager()
        {
            playerGestures = new Dictionary<System.Type, ImiGesture>();

            BaseTime = new DateTime(1970, 1, 1, 0, 0, 0);
        }

        private void initGestueProCallBacks()
        {
            ImiGestureWrapper.ImiGestureRecognizedCallBack recognizedCallback = new ImiGestureWrapper.ImiGestureRecognizedCallBack(ImiGestureRecognizedCallBack);
            recognizedPtr = Marshal.GetFunctionPointerForDelegate(recognizedCallback);

            ImiGestureWrapper.ImiGestureRecognizingCallBack recognizingCallback = new ImiGestureWrapper.ImiGestureRecognizingCallBack(ImiGestureRecognizingCallBack);
            recognizingPtr = Marshal.GetFunctionPointerForDelegate(recognizingCallback);
            Log.d("Gesture Inited!");
        }


        private static void ImiGestureRecognizedCallBack(ImiGestureWrapper.ImiGestureEventArgs s)
        {
            //Debug.Log("Native Gesture Recognized");
            if (!playerGesturesPro.ContainsKey(s.gestureType))
            {
                return;
            }
            ImiGesturePro pro = playerGesturesPro[s.gestureType];
            ImiPlayerInfo player = ImiManager.GetInstance().GetPlayerInfo((int)(s.trackingID));
            if (player == null || pro == null)
            {
                return;
            }

            threadDispatcher.Enqueue(() =>
            {
                if (pro != null && player != null)
                    pro.GestureEvent(pro, player, GestureEvent.Complete);
            });
            if (pro.GestureEventListeners != null)
            {
                threadDispatcher.Enqueue(() =>
                {
                    if (pro != null && player != null)
                        pro.GestureEventListeners.Invoke(pro, player, GestureEvent.Complete);
                });
            }

        }

        private static void ImiGestureRecognizingCallBack(ImiGestureWrapper.ImiGestureEventArgs s)
        {
            //Debug.Log("Native Gesture Recognizing");
            if (!playerGesturesPro.ContainsKey(s.gestureType))
            {
                return;
            }
            ImiGesturePro pro = playerGesturesPro[s.gestureType];
            ImiPlayerInfo player = ImiManager.GetInstance().GetPlayerInfo((int)(s.trackingID));
            if (player == null || pro == null)
            {
                return;
            }
            if (s.gestureData.progress < 1)
            {
                threadDispatcher.Enqueue(()=> {
                    if(pro != null && player != null)
                        pro.GestureEvent(pro, player, GestureEvent.Checking);
                });

                if (pro.GestureEventListeners != null)
                    threadDispatcher.Enqueue(()=> {
                        if(pro != null && player != null)
                            pro.GestureEventListeners.Invoke(pro, player, GestureEvent.Checking);
                    });
                    
            }
            else
            {
                threadDispatcher.Enqueue(()=> {
                    if(pro != null && player != null)
                        pro.GestureEvent(pro, player, GestureEvent.Complete);
                });
                if (pro.GestureEventListeners != null)
                    threadDispatcher.Enqueue(()=> {
                        if(pro != null && player != null)
                            pro.GestureEventListeners.Invoke(pro, player, GestureEvent.Complete);
                    });
            }
        }


        public void Update()
        {
            lock (locker)
            {
                playerGesturesCopy = new List<Type>(playerGestures.Keys);
            }
            playerGesturesProCopy.Clear();
            playerGesturesProCopy.AddRange(playerGesturesPro.Keys);

            refreshPlayerGesture();
            refreshNativeGesture();
            CheckGestures();
        }

        public void AddGesture(System.Type gesture, ImiGesture.OnGestureEvent listener)
        {
            lock (locker)
            {
                if (!playerGestures.ContainsKey(gesture))
                {
                    playerGestures.Add(gesture, (ImiGesture)Activator.CreateInstance(gesture));
                }
            }
            playerGestures[gesture].GestureEventListeners += listener;
        }

        private void refreshPlayerGesture()
        {
            foreach (System.Type gestureType in playerGesturesCopy)
            {
                Dictionary<int, ImiPlayerInfo> players = ImiManager.GetInstance().GetPlayerInfos();
                foreach (int key in players.Keys)
                {
                    ImiPlayerInfo player = players[key];
                    if (player == null || !player.GetPlayerTracked())
                        continue;
                    if (!player.playerGestures.ContainsKey(gestureType))
                    {
                        player.playerGestures.Add(gestureType,(ImiGesture)Activator.CreateInstance(gestureType));
                    }
                }
            }
        }

        public void refreshNativeGesture()
        {
            foreach (ImiGesturePro.GestureType gestureType in playerGesturesProCopy)
            {
                Dictionary<int, ImiPlayerInfo> players = ImiManager.GetInstance().GetPlayerInfos();
                foreach (int key in players.Keys)
                {
                    ImiPlayerInfo player = players[key];
                    if (player == null || !player.GetPlayerTracked())
                        continue;
                    AddGestureNative(player.GetUserId(), gestureType);
                }
            }
        }


        public void AddGesturePro(ImiGesturePro.GestureType gestureType, ImiGesture.OnGestureEvent listener)
        {
            lock (locker)
            {
                if (!playerGesturesPro.ContainsKey(gestureType))
                {
                    //Log.d("Adding Gestures Pro");
                    playerGesturesPro.Add(gestureType, new ImiGesturePro(gestureType));
                }
            }
            playerGesturesPro[gestureType].GestureEventListeners += listener;

        }

        private int AddGestureNative(int userId, ImiGesturePro.GestureType type)
        {
            if (!proInited)
            {
                initGestueProCallBacks();
                proInited = true;
            }
            int error = ImiGestureWrapper.ImiAddGesture((uint)userId, type, recognizedPtr, recognizingPtr);
            //if (error == 0)
                //Log.d("Adding Gesture " + type + " to user " + userId + " Succeeded");
            //else
                //Log.d("Adding Gesture failed, error = " + error);
            return error;
        }

        public void RemoveGesturePro(ImiGesturePro.GestureType gestureType)
        {
            lock (locker)
            {
                if (playerGesturesPro.ContainsKey(gestureType))
                {
                    playerGesturesPro.Remove(gestureType);
                }
            }
        }

        public bool ContainsGesturePro(ImiGesturePro.GestureType gestureType)
        {
            return playerGesturesPro.ContainsKey(gestureType);
        }


        public List<ImiGesturePro.GestureType> getPlayerGesturesPro()
        {
            return new List<ImiGesturePro.GestureType>(playerGesturesPro.Keys);
        }


        public void RemoveGesture(System.Type gesture)
        {
            lock (locker)
            {
                if (playerGestures.ContainsKey(gesture))
                {
                    playerGestures.Remove(gesture);
                }
            }
        }

       
        public bool ContainsGesture(System.Type gesture)
        {
            return playerGestures.ContainsKey(gesture);
        }

        public List<System.Type> GetPlayerGestures()
        {
            return new List<Type>(playerGestures.Keys);
        }

        private void CheckGestures()
        {
            Dictionary<int, ImiPlayerInfo> players = ImiManager.GetInstance().GetPlayerInfos();
            foreach (int key in players.Keys)
            {
                ImiPlayerInfo player = players[key];
                if (player == null || !player.GetPlayerTracked())
                    continue;
                foreach (System.Type type in playerGesturesCopy)
                {
                  checkGesture(player, type);
                }
            }
        }

        private void checkGesture(ImiPlayerInfo p, System.Type gestureType)
        {
            if (p == null || !p.playerGestures.ContainsKey(gestureType))
            {
                return;
            }
            ImiGesture g = p.playerGestures[gestureType];
            ImiGesture.State state = g.CheckGesture(p, (DateTime.Now - BaseTime).TotalSeconds);
            if (state == ImiGesture.State.FINISHED)
            {
                notifyListeners(p, gestureType, GestureEvent.Complete);
                g.Reset();
            }
            else if (state == ImiGesture.State.CHECKING)
            {
                notifyListeners(p, gestureType, GestureEvent.Checking);
            }
            else if (state == ImiGesture.State.CANCELED)
            {
                notifyListeners(p, gestureType, GestureEvent.Cancel);
                g.Reset();
            }
        }

        private void notifyListeners(ImiPlayerInfo p, System.Type gestureType, GestureEvent eventType)
        {
            ImiGesture g = p.playerGestures[gestureType];
            threadDispatcher.Enqueue(() => {
                if(g != null && p != null)
                    g.GestureEvent(g, p, eventType);
            });
            if (playerGestures.ContainsKey(gestureType) )
            {
                threadDispatcher.Enqueue(() => {
                    if(playerGestures!= null && playerGestures.ContainsKey(gestureType) && g != null && p != null)
                        playerGestures[gestureType].GestureEventListeners.Invoke(g, p, eventType);
                });
            }
        }

    }
}